//
//  UnityCaptureConnection.m
//  UnityCapture
//
//  Created by Adrian on 28.10.09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "UnityCaptureCore.h"

#import <QuickTime/QuickTime.h>

#import "FrameCompressor.h"
#import "FrameMovieExporter.h"
#import "UnityCapture.h"

#define kNumReaderObjects 20

#define kStatUpdateInterval 1

@interface UnityCaptureCore (PrivateMethods)
- (void)sendResolutionToHelper;
- (void)loadSettingsFromHelper;
- (void)prepareCapture;
- (void)startCapture;
- (void)stopCapture;
@end

@implementation UnityCaptureCore (PrivateMethods)

- (void)sendResolutionToHelper
{
	if (playerWidth == 0)
		return;
	
	[connection sendNewResolutionWithWidth:playerWidth andHeight:playerHeight];
}

- (void)loadSettingsFromHelper
{
	NSArray *settings = [connection loadSettings];
	[outputPath release];
	outputPath = [[settings objectAtIndex:0] retain];
	captureResolution = [[settings objectAtIndex:2] sizeValue];
	NSData *data = [settings objectAtIndex:1];
	// Initialize compression settings
	long flags = scAllowEncodingWithCompressionSession;
	ComponentResult theError;
	QTAtomContainer container;
	
	if (component)
		CloseComponent(component);
	component = OpenDefaultComponent(StandardCompressionType, StandardCompressionSubType);
	if(component == NULL) {
		NSLog(@"Compression component opening failed");
		return;
	}
	SCSetInfo(component, scPreferenceFlagsType, &flags);
	
	// Restore compression settings from data
	container = NewHandle([data length]);
	if(container) {
		[data getBytes:*container];
		theError = SCSetSettingsFromAtomContainer(component, container);
		if(theError)
			NSLog(@"SCSetSettingsFromAtomContainer() failed with error %i", theError);
		QTDisposeAtomContainer(container);
	}
}

- (void)prepareCapture
{
	if (playerWidth == 0 || playerHeight == 0) {
		NSLog(@"Starting capture failed: Player values not initialized.");
		return;
	}
	
	[self loadSettingsFromHelper];
	
	if (captureResolution.width > 0) {
		captureWidth  = captureResolution.width;
		captureHeight = captureResolution.height;
	} else {
		captureWidth  = playerWidth;
		captureHeight = playerHeight;
	}
	
	if (captureWidth != playerWidth || captureHeight != playerHeight) {
		waitForResolutionChangeToStartCapture = YES;
		ChangeResolutionNotification(captureWidth, captureHeight);
	} else
		[self startCapture];
}

- (void)startCapture
{
	ICMCompressionSessionOptionsRef	options;
	CodecType codec;
	double fps;
	
	options = [FrameCompressor getOptionsWithCodec:&codec frameRate:&fps fromComponent:component];
	
	// make an exporter object
	exporterObj = [[FrameMovieExporter alloc] initWithPath:outputPath codec:codec pixelsWide:captureWidth pixelsHigh:captureHeight options:options frameRate:fps];
	
	// make a frame queue controller, which will create and manage the
	// underlying set of (multiple) frame reader objects
	frameQueueController = [[QueueController alloc] initWithReaderObjects:
							kNumReaderObjects	// create this many frame reader objects
																 aContext:context
															   pixelsWide:captureWidth 
															   pixelsHigh:captureHeight];
	
	isCapturing = YES;
	beginTime = lastTime;
	currentFrame = 0;
	
	lastStatUpdate = [[NSDate new] timeIntervalSinceReferenceDate];
	framesSinceLastStatUpdate = 0;
	
	framerate = round(fps);
	
	CaptureStartNotification(framerate);
}

- (void)stopCapture
{
	isCapturing = NO;
	
	[outputPath release];
	outputPath = nil;
	CloseComponent(component);
	component = NULL;
	
	[exporterObj release];
	[frameQueueController release];
	
	CaptureEndNotification();
}

@end


@implementation UnityCaptureCore

- (id) init
{
	//Make sure client goes through designated initializer
	[self doesNotRecognizeSelector:_cmd];
	return nil;
}

- (id)initWithOpenWindow:(BOOL)open
{
	if (self = [super init]) {
		
		connection = [[UnityCaptureConnection alloc] initWithOpenWindow:open];
		
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(sendResolutionToHelper)
													 name:UCCaptureConnectionEstablishedNotification
												   object:connection];
		
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(prepareCapture)
													 name:UCStartCaptureNotification
												   object:connection];
		
		[[NSNotificationCenter defaultCenter] addObserver:self
												 selector:@selector(stopCapture)
													 name:UCStopCaptureNotification
												   object:connection];
	}
	
	return self;
}

- (void)setCGLContext:(CGLContextObj)ctx withWidth:(int)width andHeight:(int)height
{
	if (isCapturing && (context != ctx || captureWidth != width || captureHeight != height)) {
		NSLog(@"Aborting capture: Context or resolution changed.");
		[self stopCapture];
	}
	
	context = ctx;
	playerWidth = width;
	playerHeight = height;
	
	if (waitForResolutionChangeToStartCapture) {
		
		waitForResolutionChangeToStartCapture = NO;
		
		captureWidth = width;
		captureHeight = height;
		
		[self startCapture];
	}
	
	[self sendResolutionToHelper];
}

- (void)openHelperWindow
{
	[connection openHelper];
}

- (void)toggleCapture
{
	// wait for connection to open
	while (![connection connectionIsValid]) {
		usleep(1000);
	}
	[connection toggleCapture];
}

- (void)captureFrame:(float)time
{
	lastTime = time;
	
	if (!isCapturing)
		return;
	
	// Get an available reader object from the reader "free" queue
	FrameReader *freeReaderObj;
	freeReaderObj = [frameQueueController removeOldestItemFromFreeQ];
	while (!freeReaderObj) {
		usleep(1000);
		freeReaderObj = [frameQueueController removeOldestItemFromFreeQ];
	}
	if (freeReaderObj)
	{
		[freeReaderObj setBufferTime:(time - beginTime)];
		[freeReaderObj setFrameNumber:currentFrame];
		currentFrame++;
		// pass object to FrameReader and do a read operation
		// this call spawns a new thread to do the read
		[freeReaderObj readAsyncOnSeparateThread];
	} else
		NSLog(@"skipping frame: No reader available!");
	
	// see if there are available frames in the "filled" queue
	FrameReader *filledReaderObj = [frameQueueController removeOldestItemFromFilledQ];
	if (filledReaderObj)
	{
		// compress the frame and add it to our movie
		[exporterObj exportFrame:filledReaderObj];
	}
	
	framesSinceLastStatUpdate++;
	
	// update stats
	NSTimeInterval now = [[NSDate new] timeIntervalSinceReferenceDate];
	if ((now - lastStatUpdate) >= kStatUpdateInterval) {
		[connection sendStatsWithFps:(framesSinceLastStatUpdate / (now - lastStatUpdate))
							 andTime:(lastTime - beginTime)];
		framesSinceLastStatUpdate = 0;
		lastStatUpdate = [[NSDate new] timeIntervalSinceReferenceDate];
	}
}

- (void)dealloc
{	
	CloseComponent(component);
	[outputPath release];
	[connection release];
	
	[super dealloc];
}

@end
